package com.myk.game.gobangchess.rooms;

import com.myk.game.gobangchess.constants.ColorCost;
import com.myk.game.gobangchess.constants.WinMode;
import com.myk.game.gobangchess.signs.ColorSign;
import com.myk.game.gobangchess.utils.GridsUtils;
import com.myk.game.gobangchess.Room;
import com.myk.game.gobangchess.signs.AroundSign;

import java.util.*;

/**
 * 三色五子棋
 *
 */
public class ThreeColorGoBangRoom extends Room {
    private boolean isSelecting = false;
    private List<Set<Integer>> selectingPos = new ArrayList<>();
    private Set<Integer> blockedPos = new HashSet<>();
    private Set<Integer> curPlayerPos = new HashSet<>();

    private Rule curRule = new OriginRule();

    private abstract static class Rule {
        public abstract String name();

        public abstract void doRule(ThreeColorGoBangRoom room, int row, int col, int value);
    }

    private static class OriginRule extends Rule {
        @Override
        public String name() {
            return "原版规则";
        }

        @Override
        public void doRule(ThreeColorGoBangRoom room, int row, int col, int value) {
            //开局第一步白方先下一个共子
            if (room.step == 0){
                room.doRuleStep0(row, col, value);
                return;
            }

            if (!room.isSelecting) { // 判断是否要选择棋子修改
                if (room.step % 2 == 1) {  // 下共用棋
                    room.doRuleStep0(row, col, value);
                }
                else if (room.step % 2 == 0) {// 下普通棋
                    room.doRuleStep1(row, col, value);
                }
            }
            else {
                room.doRuleSelect(row, col, value);
            }
        }
    }

    private static class NewRule extends Rule {
        @Override
        public String name() {
            return "新版规则";
        }

        @Override
        public void doRule(ThreeColorGoBangRoom room, int row, int col, int value) {
            if (!room.isSelecting) { // 判断是否要选择棋子修改
                if (room.step % 2 == 0) { // 下普通棋
                    room.doRuleStep0(row, col, value);
                }
                else if (room.step % 2 == 1) { // 下共用棋
                    room.doRuleStep1(row, col, value);
                }
            }
            else {
                room.doRuleSelectNew(row, col, value);
            }
        }
    }

    public ThreeColorGoBangRoom(String roomName) {
        super(roomName, "\t\t\t\t夏日和SNG玩家设计的五子棋变种。双人三种颜色的五子棋。", "三色五子棋", GridsUtils.createEmptyGrids(16, 16), true, true);
    }

    @Override
    public String getRuleText() {
        return "开局第一步白方先下一个灰色棋子\n" +
                "之后黑白双方轮流行动：\n" +
                "每回合落1个灰色棋子+1个自己棋子\n" +
                "\n" +
                "*落下的灰子须和对方上一手灰子异线\n" +
                "\n" +
                "当一方走棋后，形成灰子四连，\n" +
                "可在每个灰子四连当中，\n" +
                "选择其余三子其中一个同化为己方棋子\n" +
                "\n" +
                "获胜条件：落子后，形成己方棋子五连\n" +
                "获胜条件：落子后，形成灰色棋子五连\n" +
                "落败条件：无棋可走";
    }

    @Override
    protected void onStartPrepare() {
        noticeWhiteMove(); //默认通知白方先行动
    }

    @Override
    public void resetGrids() {
        super.resetGrids();
        isSelecting = false;
        selectingPos = new ArrayList<>();
        blockedPos = new HashSet<>();
        curPlayerPos = new HashSet<>();
    }

    @Override
    public void doRule(int row, int col, int value) {
        this.curRule.doRule(this, row, col, value);
    }

    private void doRuleStep0(int row, int col, int value) {
        if (checkLoseBefore(value)) {
            return;
        }
        if (curGrids[row][col] != 0) { //只能落在空位置
            return;
        }
        // 不能落在一条线上
        if (blockedPos.contains(getIndex(row, col))) {
            return;
        }
        signList.clear();
        curPlayerPos.clear();
        curPlayerPos.add(getIndex(row, col));
        addNewPutSignal();
        updateBlocked(row, col);
        curGrids[row][col] = 7; //实际落子
        stepAdd();

        ; //步数统计加增加
        if (checkWin(row, col, value)) {
            return;
        }
        if (judgeLength(row, col, 4)) {
            selectingPos.clear();
            int[][] dxdy = {{0, 1}, {1, 0}, {1, 1}, {-1, 1}};
            for (int i = 0; i < 4; i++) {
                int dx = dxdy[i][0];
                int dy = dxdy[i][1];
                Set<Integer> posSet = getJudgeLength(row, col, dx, dy);
                posSet.addAll(getJudgeLength(row, col, -dx, -dy));
                if (posSet.size() >= 4) {
                    posSet.remove(getIndex(row, col));
                    selectingPos.add(posSet);
                }
            }
            isSelecting = true;
            addSelectingSignal();
            noticeAllRefreshGameInfo();
            return;
        }
        if (GridsUtils.getDataCount(curGrids, 0) == 0) {//没有胜负也没有空子了算战平
            doOver(WinMode.BOTH_DRAW, player_black.getNickNameOrTemp() + "-黑棋战平" + player_white.getNickNameOrTemp() + "-白棋");
            return;
        }
        //白方第一手先落一个共子，之后就切换行动方
        if (step == 1){
            addBlockedSignal();
            noticeBlackMove();
        }
        else{
            noticeAllRefreshGameInfo();
        }
    }

    private void doRuleStep1(int row, int col, int value) {
        if (curGrids[row][col] != 0) { //只能落在空位置
            return;
        }
        signList.clear();
        curPlayerPos.add(getIndex(row, col));
        addNewPutSignal();
        curGrids[row][col] = value; //实际落子
        stepAdd();
        ; //步数统计加增加
        if (checkWin(row, col, value)) {
            return;
        }
        if (GridsUtils.getDataCount(curGrids, 0) == 0) {//没有胜负也没有空子了算战平
            doOver(WinMode.BOTH_DRAW, player_black.getNickNameOrTemp() + "-黑棋战平" + player_white.getNickNameOrTemp() + "-白棋");
            return;
        }
        addBlockedSignal();
        if (value == 1) {
            noticeWhiteMove();
        }
        else if (value == 2) {
            noticeBlackMove();
        }
    }

    private Set<Integer> findPosSet(int row, int col) {
        for (Set<Integer> posSet : selectingPos) {
            int pos = getIndex(row, col);
            if (posSet.contains(pos)) {
                return posSet;
            }
        }
        return null;
    }

    private void doRuleSelect(int row, int col, int value) {
        Set<Integer> posSet = findPosSet(row, col);
        if (posSet == null) {
            return;
        }
        signList.clear();
        curPlayerPos.add(getIndex(row, col));
        addNewPutSignal();
        curGrids[row][col] = value;
        if (checkWin(row, col, value)) {
            return;
        }
        selectingPos.remove(posSet);
        if (selectingPos.size() == 0) {
            isSelecting = false;
        }
        else {
            addSelectingSignal();
        }
        noticeAllRefreshGameInfo();
    }

    private void doRuleSelectNew(int row, int col, int value) {
        Set<Integer> posSet = findPosSet(row, col);
        if (posSet == null) {
            return;
        }
        signList.clear();
        curPlayerPos.add(getIndex(row, col));
        addNewPutSignal();
        for (int index : posSet) {
            int[] pos = getPositionByIndex(index);
            curGrids[pos[0]][pos[1]] = 0;
        }
        curGrids[row][col] = value;
        if (checkWin(row, col, value)) {
            return;
        }
        selectingPos.remove(posSet);
        if (selectingPos.size() == 0) {
            isSelecting = false;
        }
        else {
            addSelectingSignal();
        }
        noticeAllRefreshGameInfo();
    }

    //判断是否形成连length
    protected boolean judgeLength(int x, int y, int length) {
        int[][] dxdy = {{0, 1}, {1, 0}, {1, 1}, {-1, 1}};
        for (int i = 0; i < 4; i++) {
            int dx = dxdy[i][0];
            int dy = dxdy[i][1];
            Set<Integer> posSet = getJudgeLength(x, y, dx, dy);
            posSet.addAll(getJudgeLength(x, y, -dx, -dy));
            if (posSet.size() >= length) {
                return true;
            }
        }
        return false;
    }

    protected Set<Integer> getJudgeLength(int x, int y, int dx, int dy) {
        int value = curGrids[x][y];
        Set<Integer> posSet = new HashSet<>();
        while (x >= 0 && y >= 0 && x < curGrids.length && y < curGrids.length) {
            if (curGrids[x][y] != value) {
                return posSet;
            }
            posSet.add(getIndex(x, y));
            x += dx;
            y += dy;
        }
        return posSet;
    }

    private boolean checkLoseBefore(int value) {
        for (int i = 0; i < curGrids.length; i++) {
            for (int j = 0; j < curGrids[0].length; j++) {
                if (curGrids[i][j] == 0 && (!blockedPos.contains(getIndex(i, j)))) {
                    return false;
                }
            }
        }
        if (value == 2) {
            doOver(WinMode.BLACK_WIN, "白方无法落子," + player_black.getNickNameOrTemp() + "执黑棋战胜" + player_white.getNickNameOrTemp());
        }
        else if (value == 1) {
            doOver(WinMode.WHITE_WIN, "黑方无法落子," + player_white.getNickNameOrTemp() + "执白棋战胜" + player_black.getNickNameOrTemp());
        }
        return true;
    }

    private boolean checkWin(int row, int col, int value) {
        if (judgeLength(row, col, 5)) {//连5成功
            if (value == 1) {
                doOver(WinMode.BLACK_WIN, player_black.getNickNameOrTemp() + "执黑棋战胜" + player_white.getNickNameOrTemp());
            }
            else if (value == 2) {
                doOver(WinMode.WHITE_WIN, player_white.getNickNameOrTemp() + "执白棋战胜" + player_black.getNickNameOrTemp());
            }
            return true;
        }
        return false;
    }

    private void updateBlocked(int row, int col) {
        blockedPos.clear();
        int[][] dxdy = {{0, 1}, {1, 0}, {1, 1}, {-1, 1}};
        for (int i = 0; i < 4; i++) {
            int dx = dxdy[i][0], dy = dxdy[i][1];
            for (int x = row, y = col; x < curGrids.length && x >= 0 && y < curGrids[0].length && y >= 0; x += dx, y += dy) {
                if (curGrids[x][y] == 0) {
                    blockedPos.add(getIndex(x, y));
                }
            }
            for (int x = row, y = col; x < curGrids.length && x >= 0 && y < curGrids[0].length && y >= 0; x -= dx, y -= dy) {
                if (curGrids[x][y] == 0) {
                    blockedPos.add(getIndex(x, y));
                }
            }
        }
    }

    public void addNewPutSignal() {
        for (int index : curPlayerPos) {
            signList.add(new AroundSign(index, "#00000000", "#6600FF00", 1.2f));
        }
    }

    public void addSelectingSignal() {
        for (Set<Integer> indexSet : selectingPos) {
            signList.add(new AroundSign(new ArrayList<>(indexSet), "#660000FF", "#660000FF", 1.2f));
        }

    }

    public void addBlockedSignal() {
        for (Integer index : blockedPos) {
            signList.add(new ColorSign(index, ColorCost.GRAY_TRANSLUCENT));
        }
    }
}
